{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "-028b2NqE02R",
   "metadata": {
    "executionInfo": {
     "elapsed": 341,
     "status": "ok",
     "timestamp": 1693232083105,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "-028b2NqE02R"
   },
   "outputs": [],
   "source": [
    "\"\"\"NOTES: Batch data is different each time in keras, which result in slight differences in results.\"\"\"\n",
    "\"\"\"\n",
    "\" Bettycxh, \"Toward-Sleep-Apnea-Detection-with-Lightweight-Multi-scaled-Fusion-Network,\" GitHub repository, n.d. [Online]. \n",
    "Available: https://github.com/Bettycxh/Toward-Sleep-Apnea-Detection-with-Lightweight-Multi-scaled-Fusion-Network\n",
    "\"\"\"\n",
    "import pickle\n",
    "\n",
    "import keras\n",
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "import os\n",
    "from keras.callbacks import LearningRateScheduler"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "a034672d",
   "metadata": {
    "executionInfo": {
     "elapsed": 1150,
     "status": "ok",
     "timestamp": 1693232086282,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "a034672d"
   },
   "outputs": [],
   "source": [
    "from keras.layers import Conv1D, Dense, Dropout, Flatten, MaxPooling1D\n",
    "from tensorflow.keras.layers import Input\n",
    "from tensorflow.keras.models import Model\n",
    "from keras.regularizers import l2\n",
    "from scipy.interpolate import splev, splrep\n",
    "import pandas as pd\n",
    "from sklearn.model_selection import train_test_split"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "591596f3",
   "metadata": {
    "executionInfo": {
     "elapsed": 312,
     "status": "ok",
     "timestamp": 1693232087740,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "591596f3"
   },
   "outputs": [],
   "source": [
    "base_dir = \"dataset\"\n",
    "\n",
    "ir = 3 # interpolate interval\n",
    "before = 2\n",
    "after = 2\n",
    "\n",
    "# normalize\n",
    "scaler = lambda arr: (arr - np.min(arr)) / (np.max(arr) - np.min(arr))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "0cef782d",
   "metadata": {
    "executionInfo": {
     "elapsed": 303,
     "status": "ok",
     "timestamp": 1693232089719,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "0cef782d"
   },
   "outputs": [],
   "source": [
    "from scipy.interpolate import CubicSpline\n",
    "def interpolate_numpy_array(arr, desired_length):\n",
    "    cs = CubicSpline(np.linspace(0, 1, len(arr)), arr)\n",
    "    x_new = np.linspace(0, 1, desired_length)\n",
    "    interpolated_arr = cs(x_new)\n",
    "    return interpolated_arr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "KXj-pyPoXKm3",
   "metadata": {
    "executionInfo": {
     "elapsed": 313,
     "status": "ok",
     "timestamp": 1693232167282,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "KXj-pyPoXKm3"
   },
   "outputs": [],
   "source": [
    "import random\n",
    "def load_data():\n",
    "    tm = np.arange(0, (before + 1 + after) * 60, step=1 / float(ir))\n",
    "    with open(os.path.join(base_dir, \"T_1.pkl\"), 'rb') as f: # read preprocessing result\n",
    "        apnea_ecg = pickle.load(f)\n",
    "    x_train1,x_train2,x_train3 = [],[],[]\n",
    "    o_train, y_train = apnea_ecg[\"o_train\"], apnea_ecg[\"y_train\"]\n",
    "    groups_train = apnea_ecg[\"groups_train\"]\n",
    "    for i in range(len(o_train)):\n",
    "        min_distance_list, max_distance_list, mean_distance_list = o_train[i]\n",
    "\t\t# Curve interpolation\n",
    "        min_distance_list_inter = interpolate_numpy_array(min_distance_list,900)\n",
    "        max_distance_list_inter = interpolate_numpy_array(max_distance_list,900)\n",
    "        mean_distance_list_inter = interpolate_numpy_array(mean_distance_list,900)\n",
    "\"\"\"\n",
    "In this part we design the ablation to test the relationship of each terms: MinDP,MaxDP, MeanDP. By add this value in\n",
    "the list x_train1,x_train2,x_train3 we can represent all the case in the paper including: M1,M2,M3,M4,M5,M6,M7.\n",
    "MinDP: min_distance_list_inter\n",
    "MaxDP: max_distance_list_inter\n",
    "MeanDP: mean_distance_list_inter\n",
    "\"\"\"\n",
    "        x_train1.append([min_distance_list_inter, max_distance_list_inter])\n",
    "        x_train2.append([min_distance_list_inter[180:720], max_distance_list_inter[180:720]])\n",
    "        x_train3.append([min_distance_list_inter[360:540], max_distance_list_inter[360:540]])\n",
    "    x_training1,x_training2,x_training3,y_training,groups_training = [],[],[],[],[]\n",
    "    x_val1,x_val2,x_val3,y_val,groups_val = [],[],[],[],[]\n",
    "\n",
    "    trainlist = random.sample(range(len(o_train)),int(len(o_train)*0.7))\n",
    "    num=[i for i in range(16713)]\n",
    "    vallist = set(num) - set(trainlist)\n",
    "    vallist = list(vallist)\n",
    "    for i in trainlist:\n",
    "        x_training1.append(x_train1[i])\n",
    "        x_training2.append(x_train2[i])\n",
    "        x_training3.append(x_train3[i])\n",
    "        y_training.append(y_train[i])\n",
    "        groups_training.append(groups_train[i])\n",
    "    for i in vallist:\n",
    "        x_val1.append(x_train1[i])\n",
    "        x_val2.append(x_train2[i])\n",
    "        x_val3.append(x_train3[i])\n",
    "        y_val.append(y_train[i])\n",
    "        groups_val.append(groups_train[i])\n",
    "\n",
    "    x_training1 = np.array(x_training1, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_training2 = np.array(x_training2, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_training3 = np.array(x_training3, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    y_training = np.array(y_training, dtype=\"float32\")\n",
    "    x_val1 = np.array(x_val1, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_val2 = np.array(x_val2, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_val3 = np.array(x_val3, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    y_val = np.array(y_val, dtype=\"float32\")\n",
    "\n",
    "    x_test1,x_test2,x_test3 = [],[],[]\n",
    "    o_test, y_test = apnea_ecg[\"o_test\"], apnea_ecg[\"y_test\"]\n",
    "    groups_test = apnea_ecg[\"groups_test\"]\n",
    "    for i in range(len(o_test)):\n",
    "        min_distance_list, max_distance_list, mean_distance_list = o_test[i]\n",
    "\t\t# Curve interpolation\n",
    "        min_distance_list_inter = interpolate_numpy_array(min_distance_list,900)\n",
    "        max_distance_list_inter = interpolate_numpy_array(max_distance_list,900)\n",
    "        mean_distance_list_inter = interpolate_numpy_array(mean_distance_list,900)\n",
    "        x_test1.append([min_distance_list_inter, max_distance_list_inter])\n",
    "        x_test2.append([min_distance_list_inter[180:720], max_distance_list_inter[180:720]])\n",
    "        x_test3.append([min_distance_list_inter[360:540], max_distance_list_inter[360:540]])\n",
    "    x_test1 = np.array(x_test1, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_test2 = np.array(x_test2, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    x_test3 = np.array(x_test3, dtype=\"float32\").transpose((0, 2, 1))\n",
    "    y_test = np.array(y_test, dtype=\"float32\")\n",
    "\n",
    "    return x_training1, x_training2, x_training3, y_training, groups_training, x_val1, x_val2, x_val3, y_val, groups_val, x_test1, x_test2, x_test3, y_test, groups_test"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "GnDFx0fGCy1m",
   "metadata": {
    "executionInfo": {
     "elapsed": 318,
     "status": "ok",
     "timestamp": 1693232096445,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "GnDFx0fGCy1m"
   },
   "outputs": [],
   "source": [
    "from keras.layers import Dropout, MaxPooling1D, Reshape, multiply, Conv1D, GlobalAveragePooling1D, Dense\n",
    "def create_model(input_a_shape, input_b_shape, input_c_shape, weight=1e-3):\n",
    "    # SA-CNN-3\n",
    "    input1 = Input(shape=input_a_shape)\n",
    "    x1 = Conv1D(16, kernel_size=11, strides=1, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(weight), bias_regularizer=l2(weight))(input1)\n",
    "    x1 = Conv1D(24, kernel_size=11, strides=2, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x1)\n",
    "    x1 = MaxPooling1D(pool_size=3, padding=\"same\")(x1)\n",
    "    x1 = Conv1D(32, kernel_size=11, strides=1, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x1)\n",
    "    x1 = MaxPooling1D(pool_size=5, padding=\"same\")(x1)\n",
    "\n",
    "    # SA-CNN-2\n",
    "    input2 = Input(shape=input_b_shape)\n",
    "    x2 = Conv1D(16, kernel_size=11, strides=1, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(weight), bias_regularizer=l2(weight))(input2)\n",
    "    x2 = Conv1D(24, kernel_size=11, strides=2, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x2)\n",
    "    x2 = MaxPooling1D(pool_size=3, padding=\"same\")(x2)\n",
    "    x2 = Conv1D(32, kernel_size=11, strides=3, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x2)\n",
    "\n",
    "    # SA-CNN-1\n",
    "    input3 = Input(shape=input_c_shape)\n",
    "    x3 = Conv1D(16, kernel_size=11, strides=1, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(weight), bias_regularizer=l2(weight))(input3)\n",
    "    x3 = Conv1D(24, kernel_size=11, strides=2, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x3)\n",
    "    x3 = MaxPooling1D(pool_size=3, padding=\"same\")(x3)\n",
    "    x3 = Conv1D(32, kernel_size=1, strides=1, padding=\"same\", activation=\"relu\", kernel_initializer=\"he_normal\",\n",
    "                kernel_regularizer=l2(1e-3), bias_regularizer=l2(weight))(x3)\n",
    "\n",
    "    # Channel-wise attention module\n",
    "    concat = keras.layers.concatenate([x1, x2, x3], name=\"Concat_Layer\", axis=-1)\n",
    "    squeeze = GlobalAveragePooling1D()(concat)\n",
    "    excitation = Dense(48, activation='relu')(squeeze)\n",
    "    excitation = Dense(96, activation='sigmoid')(excitation)\n",
    "    excitation = Reshape((1, 96))(excitation)\n",
    "    scale = multiply([concat, excitation])\n",
    "    x = GlobalAveragePooling1D()(scale)\n",
    "    dp = Dropout(0.5)(x)\n",
    "    outputs = Dense(2, activation='softmax', name=\"Output_Layer\")(dp)\n",
    "    model = Model(inputs=[input1, input2, input3], outputs=outputs)\n",
    "    return model"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "6c830eb3",
   "metadata": {
    "executionInfo": {
     "elapsed": 308,
     "status": "ok",
     "timestamp": 1693232100397,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "6c830eb3"
   },
   "outputs": [],
   "source": [
    "def lr_schedule(epoch, lr):\n",
    "    if epoch > 70 and \\\n",
    "            (epoch - 1) % 10 == 0:\n",
    "        lr *= 0.1\n",
    "    print(\"Learning rate: \", lr)\n",
    "    return lr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "ded3f606",
   "metadata": {
    "executionInfo": {
     "elapsed": 1,
     "status": "ok",
     "timestamp": 1693232101699,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "ded3f606"
   },
   "outputs": [],
   "source": [
    "def plot(history):\n",
    "    \"\"\"Plot performance curve\"\"\"\n",
    "    fig, axes = plt.subplots(1, 2, figsize=(10, 4))\n",
    "    axes[0].plot(history[\"loss\"], \"r-\", history[\"val_loss\"], \"b-\", linewidth=0.5)\n",
    "    axes[0].set_title(\"Loss\")\n",
    "    axes[1].plot(history[\"accuracy\"], \"r-\", history[\"val_accuracy\"], \"b-\", linewidth=0.5)\n",
    "    axes[1].set_title(\"Accuracy\")\n",
    "    fig.tight_layout()\n",
    "    plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 18,
   "id": "v6XhEYeu7cgM",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 45661,
     "status": "ok",
     "timestamp": 1693233004292,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "v6XhEYeu7cgM",
    "outputId": "4f452c32-a1f2-4c9e-b7bc-05ca348f3613"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "input_shape (11699, 900, 2) (11699, 540, 2) (11699, 180, 2)\n"
     ]
    }
   ],
   "source": [
    "from keras import optimizers\n",
    "if __name__ == \"__main__\":\n",
    "\n",
    "    x_train1, x_train2, x_train3, y_train, groups_train, x_val1, x_val2, x_val3, y_val, groups_val, x_test1, x_test2, x_test3, y_test, groups_test= load_data()\n",
    "\n",
    "    y_train = keras.utils.to_categorical(y_train, num_classes=2)  # Convert to two categories\n",
    "    y_val = keras.utils.to_categorical(y_val, num_classes=2)\n",
    "    y_test = keras.utils.to_categorical(y_test, num_classes=2)\n",
    "\n",
    "    print('input_shape', x_train1.shape, x_train2.shape, x_train3.shape)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "9b0daa1e",
   "metadata": {},
   "outputs": [],
   "source": [
    "from keras.utils import to_categorical\n",
    "from sklearn.metrics import confusion_matrix, f1_score,roc_auc_score\n",
    "\n",
    "\n",
    "model = create_model(x_train1.shape[1:], x_train2.shape[1:], x_train3.shape[1:])\n",
    "model.summary()\n",
    "\n",
    "model.compile(optimizer='adam', loss=\"categorical_crossentropy\", metrics=['accuracy'])\n",
    "\n",
    "lr_scheduler = LearningRateScheduler(lr_schedule) # Dynamic adjustment learning rate\n",
    "history = model.fit([x_train1, x_train2, x_train3], y_train, batch_size=128, epochs=100, validation_data=([x_val1, x_val2, x_val3], y_val),\n",
    "                        callbacks=[lr_scheduler])\n",
    "\n",
    "# evaluate the model\n",
    "loss, accuracy = model.evaluate([x_test1, x_test2, x_test3], y_test)\n",
    "print(\"Test loss: \", loss)\n",
    "print(\"Accuracy: \", accuracy)\n",
    "\n",
    "# print accuracy, sensitivity, specificity, and F1 score for both training and testing data\n",
    "print(\"Testing:\")\n",
    "y_true, y_pred = y_test.argmax(axis=-1), np.argmax(model.predict([x_test1, x_test2, x_test3], batch_size=1024, verbose=1), axis=-1)\n",
    "y_true = to_categorical(y_true, num_classes=2)  # Convert y_true to binary label indicators\n",
    "C = confusion_matrix(y_true[:, 1], y_pred, labels=(1, 0))  # Use y_true[:, 1] as binary label indicators for class 1\n",
    "TP, TN, FP, FN = C[0, 0], C[1, 1], C[1, 0], C[0, 1]\n",
    "acc, sn, sp = 1. * (TP + TN) / (TP + TN + FP + FN), 1. * TP / (TP + FN), 1. * TN / (TN + FP)\n",
    "precision = TP / (TP + FP)\n",
    "recall = sn  # Recall is equivalent to sensitivity\n",
    "f1 = 2 * (precision * recall) / (precision + recall)\n",
    "print(\"TP:{}, TN:{}, FP:{}, FN:{}, loss{}, acc{}, sn{}, sp{}, f1{}\".format(TP, TN, FP, FN,loss, acc, sn, sp, f1))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 28,
   "id": "4a01665c",
   "metadata": {
    "colab": {
     "base_uri": "https://localhost:8080/"
    },
    "executionInfo": {
     "elapsed": 3474,
     "status": "ok",
     "timestamp": 1693234327970,
     "user": {
      "displayName": "Hiếu Nguyễn Xuân",
      "userId": "09184859202144170734"
     },
     "user_tz": -420
    },
    "id": "4a01665c",
    "outputId": "7d1cfbce-fcc0-481e-a0db-84dfc64691e6"
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "530/530 [==============================] - 2s 3ms/step\n"
     ]
    }
   ],
   "source": [
    "#save model \n",
    "y_score = model.predict([x_test1, x_test2, x_test3])\n",
    "output = pd.DataFrame({\"y_true\": y_test[:, 1], \"y_score\": y_score[:, 1], \"subject\": groups_test})\n",
    "output.to_csv(os.path.join(\"output\", \"file_name.csv\"), index=False)"
   ]
  }
 ],
 "metadata": {
  "accelerator": "GPU",
  "colab": {
   "gpuType": "T4",
   "provenance": []
  },
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.17"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
